/*
* Copyright (c) 2001 Sun Microsystems, Inc.  All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Sun Microsystems, Inc. for Project JXTA."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Sun", "Sun Microsystems, Inc.", "JXTAre" and "Project JXTA"
* must not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact Project JXTA at http://www.jxta.org.
*
* 5. Products derived from this software may not be called "JXTA",
* nor may "JXTA" appear in their name, without prior written
* permission of Sun.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of Project JXTA.  For more
* information on Project JXTA, please see
* <http://www.jxta.org/>.
*
* This license is based on the BSD license adopted by the Apache Foundation.
*
* $Id: Group.java,v 1.17 2006/07/07 21:53:36 nano Exp $
*/

package net.jxta.myjxta.util;

import net.jxta.id.ID;
import net.jxta.myjxta.MyJXTA;
import net.jxta.myjxta.util.objectmodel.GroupNode;
import net.jxta.myjxta.util.objectmodel.JxtaNode;
import net.jxta.myjxta.util.objectmodel.MyJxtaObjectRepository;
import net.jxta.myjxta.util.objectmodel.PeerNode;
import net.jxta.peergroup.PeerGroup;
import net.jxta.pipe.PipeID;
import net.jxta.protocol.PeerGroupAdvertisement;
import net.jxta.rendezvous.RendezVousService;
import net.jxta.rendezvous.RendezvousEvent;
import net.jxta.rendezvous.RendezvousListener;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ResourceBundle;

/**
 * @author james todd [gonzo at jxta dot org]
 * @version $Id: Group.java,v 1.17 2006/07/07 21:53:36 nano Exp $
 */
public class Group
        implements RendezvousListener {

    private final class ShutdownThread extends Thread {
        private PeerGroup tmp;

        private ShutdownThread(PeerGroup tmp) {
            this.tmp = tmp;
        }

        public void run() {

            String name = tmp.getPeerGroupName();
            System.out.println(name + " shudown initiated...");
            tmp.stopApp();
            tmp.unref();
            tmp = null;
            System.out.println(name + " shudown done!");
        }
    }

    private static final ResourceBundle STRINGS = Resources.getStrings();

    public static final String RENDEZVOUS =
            STRINGS.getString("status.rendezvous.service");
    public static final String CONNECT =
            STRINGS.getString("status.rendezvous.connect");
    public static final String DISCONNECT =
            STRINGS.getString("status.rendezvous.disconnect");

    /**
     * The MyJxta instance to use for application wide parameters
     */
    private MyJXTA myjxta = null;

    /**
     * The PeerGroup of this node. Only non-null if the group is joined
     */
    private PeerGroup pg = null;

    /**
     * The PeerGroupAdvertisement describing this group
     */
    private PeerGroupAdvertisement pga = null;
    private RendezVousService rendezVous;

    /**
     * The parent group of this node, which may be null
     */
    private Group parentGroup = null;

    /**
     * The dialogs hooked to the UI
     */
    private List<GroupListener> listeners = null;

    private boolean isVisible = true;
    private int autoRendezVousPeriod = Constants.AUTO_RENDEZVOUS_JOIN_PERIOD;

    private ID m_ownPeersCommandId;

    /**
     * Create a new Group instance
     *
     * @param myjxta the MyJxta instance for application wide parameters
     * @param pga    the PeerGroupAdvertisement describing this group
     * @param parent the parent group of this group
     */
    public Group(MyJXTA myjxta, PeerGroupAdvertisement pga, Group parent) {
        this(myjxta, parent);

        this.pga = pga;
    }

    /**
     * Create a new Group instance
     *
     * @param myjxta the MyJxta instance for application wide parameters
     * @param pg     the joined PeerGroup of this group
     * @param parent the parent group of this group
     */
    public Group(MyJXTA myjxta, PeerGroup pg, Group parent) {
        this(myjxta, parent);

        setPeerGroup(pg);
    }

    /**
     * Create a new Group instance
     *
     * @param myjxta  the MyJxta instance for application wide parameters
     * @param pg      the joined PeerGroup of this group
     * @param parent  the parent group of this group
     * @param visible visibility of this group
     * @author 2005-21-06 jamoore.jxta.org create constructor with visibility
     * param. fixes myjxta2 issue#286
     */
    public Group(MyJXTA myjxta, PeerGroup pg, Group parent, boolean visible) {
        this(myjxta, parent);

        this.setVisible(visible);

        setPeerGroup(pg);
    }

    /**
     * Create a new Group instance
     *
     * @param myjxta the MyJxta instance for application wide parameters
     * @param parent the parent group of this group
     */
    protected Group(MyJXTA myjxta, Group parent) {
        this.myjxta = myjxta;
        this.parentGroup = parent;
        this.isVisible = this.parentGroup == null || this.parentGroup.isVisible();
    }

    /**
     * Return the PeerGroupAdvertisement describing the group
     *
     * @return the PeerGroupAdvertisement describing the group
     */
    public PeerGroupAdvertisement getPeerGroupAdvertisement() {
        return this.pga;
    }

    /**
     * Return the PeerGroup of this node. This is non-null only if the
     * peer actually joined the group
     *
     * @return the PeerGroup of this node, which may be null
     */
    public PeerGroup getPeerGroup() {
        return this.pg;
    }

    /**
     * Sets the PeerGroup for this node
     *
     * @param pg the new PeerGroup
     */
    public void setPeerGroup(PeerGroup pg) {
        if (pg != null) {
            if (isJoined()) {
                resign(false);
            }

            this.pg = pg;
            this.pga = pg.getPeerGroupAdvertisement();
            GroupNode node = new GroupNode(this);
            GroupNode parentNode=parentGroup!=null?new GroupNode(parentGroup):null;
            MyJxtaObjectRepository.getObjectRepository().add(node, parentNode);


            addListener(this.myjxta);

            this.rendezVous = pg.getRendezVousService();

            this.rendezVous.addListener(this);

            if (this.rendezVous != null) {
                if (this.rendezVous.isRendezVous()) {
                    notifyListeners(RENDEZVOUS, null);
                } else if (this.rendezVous.isConnectedToRendezVous()) {
                    notifyListeners(CONNECT, null);
                }
            }
        }
    }

    /**
     * Get the name of this group
     *
     * @return the name of this group
     */
    public String getName() {
        return this.pga.getName();
    }

    /**
     * Get the id of this group adv (not necessarily GID; just unique key).
     *
     * @return the id of this group adv.
     */
    public String getId() {
        return this.pga.getID().toString();
    }

    public int getAutoRendezVousPeriod() {
        return this.autoRendezVousPeriod;
    }

    public void setAutoRendezVousPeriod(int p) {
        this.autoRendezVousPeriod = p;
    }

    /**
     * Return the description of this group
     *
     * @return the description of this group
     */
    public String getDescription() {
        return this.pga.getDescription();
    }

    /**
     * Add a new JxtaNode to the general view
     *
     * @param node the node to add
     */
    public void add(JxtaNode node) {
        // xxx: assume peer
        if (node instanceof PeerNode) {
            this.myjxta.addJxtaNode(node);
        }
    }

    /**
     * Check whether the underlying group was joined by this peer
     *
     * @return true if the group was already joined, false otherwise
     */
    public boolean isJoined() {
        return this.pg != null;
    }

    /**
     * Resign from a previously joind group
     */
    public void resign(boolean p_separateThread) {
        if (isJoined()) {
            PeerGroup tmp = this.pg;
            this.pg = null;
            clearListeners();
            if (p_separateThread) {
                new ShutdownThread(tmp).start();
            } else {
                new ShutdownThread(tmp).run();
            }

        }
    }

    /**
     * Return the parent of this group, which might be null
     *
     * @return the parent of this group
     */
    public Group getParentGroup() {
        return this.parentGroup;
    }

    public void addListener(GroupListener gl) {
        if (gl != null) {
            if (this.listeners == null) {
                this.listeners = new ArrayList<GroupListener>();
            }

            this.listeners.add(gl);
        }
    }

    public void removeListener(GroupListener gl) {
        if (this.listeners != null) {
            this.listeners.remove(gl);
            if (this.listeners.size() == 0) {
                clearListeners();
            }
        }
    }

    public void clearListeners() {
        if (this.listeners != null) {
            this.listeners.clear();
            this.listeners = null;
        }
    }

    public boolean isVisible() {
        return this.isVisible;
    }

    public void setVisible(boolean isVisible) {
        this.isVisible = isVisible;
    }

    public boolean isOwnPeerRdv() {
        return this.rendezVous.isRendezVous();
    }

    public boolean isConnected() {
        return this.pg != null &&
                this.rendezVous != null &&
                (this.rendezVous.isConnectedToRendezVous() ||
                        this.rendezVous.isRendezVous());
    }

    public void rendezvousEvent(RendezvousEvent re) {
        switch (re.getType()) {
            case RendezvousEvent.RDVCONNECT:
                if (this.rendezVous.isConnectedToRendezVous()) {
                    notifyListeners(CONNECT, re);
                } else {
                    notifyListeners(null, re);
                }
                break;
            case RendezvousEvent.RDVRECONNECT:
                notifyListeners(null, re);
                break;
            case RendezvousEvent.RDVDISCONNECT:
            case RendezvousEvent.RDVFAILED:
                if (! this.rendezVous.isConnectedToRendezVous()) {
                    notifyListeners(DISCONNECT, re);
                } else {
                    notifyListeners(null, re);
                }
                break;
            default:
                notifyListeners(null, re);
        }
    }

    /**
     * Check whether two group objects are equal. They are equal if
     * the value returned by {@link #getId() getID} are equal
     *
     * @return true if the two group objects are equal, false otherwise
     */
    public boolean equals(Object o) {
        return (o instanceof Group &&
                getId().equals(((Group) o).getId()));
    }

    /**
     * hash method must be over-ridden if equals() is over-ridden.
     * The Object api says that if two objects evaulate as equal using
     * the equals() method, then their hash codes must be equal.
     * <p/>
     * This hash method ensures that the hash codes reflect the
     * information evaluated by equals.
     *
     * @return hash code for this GroupNode.
     */
    public int hashCode() {
        return getId().hashCode();
    }


    private void notifyListeners(String s, RendezvousEvent p_re) {
        s = s != null ? s.trim() : null;


        if (this.listeners != null && this.listeners.size() > 0) {
            for (Iterator<GroupListener> iterator = listeners.iterator(); iterator.hasNext();) {
                GroupListener groupListener = iterator.next();
                groupListener.groupEvent(this, s, p_re);
            }
        }
    }

    /**
     * Sets the owns peer command pipeId inside this group
     *
     * @param p_peerID
     */
    public void setOwnCommandId(ID p_peerID) {
        m_ownPeersCommandId = p_peerID;
    }

    public PipeID getOwnPeersCommandId() {
        return (PipeID) m_ownPeersCommandId;
    }

    public static GroupNode getGroupNode(ID group_id) {
        return MyJxtaObjectRepository.getObjectRepository().findGroupNode(group_id);
    }

}
